﻿using System;
using System.Collections;
using System.Text.RegularExpressions;

namespace CommonEngine.Helper
{
    /// <summary>
    /// XSS攻击安全（文本）过滤，核心库
    /// </summary>
    public class XSSHelper
    {
        /// <summary>
        /// 版本
        /// </summary>
        public const string VER = "2.0.0.5";
        /// <summary>
        /// 允许的样式
        /// </summary>
        public static string DEFAULT_ALLOW_STYLE_PROPERTYS = "color,text-align,font-weight,font-size";

        /// <summary>
        /// 富文本过滤Xss
        /// </summary>
        /// <param name="text">要过滤的参数</param>
        /// <returns></returns>
        public static string RichTextFilter(string text)
        {
            try
            {
                if (text == null || text.Length < 1)
                {
                    return text;
                }
                //去正则干扰
                //text = text.Replace("\r\n", "").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace("\b", "").Replace("\f", "").Replace("\0","");

                //Tag黑名单
                //colgroup table td tr(ctrl+v表格要求)
                text = Regex.Replace(text, @"((?:&#0*60;|\\u003c|&#x0*3c;|<|&lt;|\\x0*3c;|\\0*74;))", "<", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"((?:&#0*62;|\\u003e|&#x0*3e;|>|&gt;|\\x0*3e;|\\0*76;))", ">", RegexOptions.IgnoreCase);

                text = Regex.Replace(text, @"<\s*/?\s*(tbody|th|marquee|form|frameset|frame|noframes|iframe|input|col|caption|textarea|svg|text|animate|isindex|inputautofocus|audio|keygen|button|object).*?>", " ", RegexOptions.IgnoreCase);
                //Style白名单
                //html = Regex.Replace(html, @"(<(?!img)[^>]*)\bstyle\s*=\s*[""|'](?!color|text-align|font-weight|font-size)[^""|']*[^>]*(>)", "$1$2", RegexOptions.IgnoreCase);

                //去除注释
                var matchs = Regex.Matches(text, @"\/\*(.*)\*\/", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = "";

                    text = text.Replace(tag, newtag);
                }
                //去除注释
                matchs = Regex.Matches(text, @"<!--(.*)-->", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = "";

                    text = text.Replace(tag, newtag);
                }
                //去除假空(恶意注入)
                matchs = Regex.Matches(text, @"<[^>]+?(?:\s|(?:&nbsp;)|(?:&#0*160;)|(?:\\u0020)|(?:&#x0*20;)){2,}[^>]+?>", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = Regex.Replace(tag, @"\s|&nbsp;|&#0*160;|\\u0020|&#x0*20;", " ", RegexOptions.IgnoreCase); newtag = Regex.Replace(newtag, @"\s{2,}", " ");

                    text = text.Replace(tag, newtag);
                }
                text = Regex.Replace(text, @"autofocus", "autofoucs", RegexOptions.IgnoreCase);//html5

                //规范标签(去干扰)
                text = Regex.Replace(text, @"(?:&#0*61;)|(?:\\u003d)|(?:&#x0*3d;)", "=", RegexOptions.IgnoreCase);        //等号
                text = Regex.Replace(text, @"\s*=\s*", "=");
                text = Regex.Replace(text, @"\s*/\s*>", " />");//规范结尾标签
                text = Regex.Replace(text, @"<\s+", "<");      //规范开标签
                text = Regex.Replace(text, @"\s+>", ">");      //规范闭标签

                //剔除Tab键
                matchs = Regex.Matches(text, @"((?:&#0*9;|\\u0009|&#x0*9;|&tab;|\\x0*9;|\\0*9;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                //剔除回车
                //matchs = Regex.Matches(text, @"((?:&#0*13;|\\u0013|&#x0*d;|\\x0*d;|\\0*15;))", RegexOptions.IgnoreCase);
                //for (int i = 0; i < matchs.Count; i++)
                //{
                //    string tag = matchs[i].Value;
                //    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                //    text = text.Replace(tag, newtag);
                //}
                //剔除换行
                //matchs = Regex.Matches(text, @"((?:&#0*10;|\\u0010|&#x0*a;|\\x0*a;|\\0*12;))", RegexOptions.IgnoreCase);
                //for (int i = 0; i < matchs.Count; i++)
                //{
                //    string tag = matchs[i].Value;
                //    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                //    text = text.Replace(tag, newtag);
                //}
                ////对各种变态、异型js脚本注入的秒杀方案
                matchs = Regex.Matches(text, @"<[^<>]*((?:s|&#0*115;|&#0*83;|\\u0073|\\u0053|&#x0*73;|&#x0*53;)(?:c|&#0*99;|&#0*67;|\\u0063|\\u0043|&#x0*63;|&#x0*43;)(?:r|&#0*114;|&#0*82;|\\u0072|\\u0052|&#x0*72;|&#x0*52;)(?:i|&#0*105;|&#0*73;|\\u0069|\\u0049|&#x0*69;|&#x0*49;)(?:p|&#0*112;|&#0*80;|\\u0070|\\u0050|&#x0*70;|&#x0*50;)(?:t|&#0*116;|&#0*84;|\\u0074|\\u0054|&#x0*74;|&#x0*54;))+[^<>]*>", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "XssScript");
                    //string newtag = tag.Replace("<", "&lt;").Replace(">", "&gt;");//强显
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"<[^<>]+((?:e|&#0*101;|\\u0065|&#x0*65;)(?:v|&#0*118;|\\u0076|&#x0*76;)(?:a|&#0*97;|\\u0061|&#x0*61;)(?:l|&#0*108;|\\u006c|&#x0*6c;))+[^<>]+>", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "XssEval");
                    //string newTag = tag.Replace("<", "&lt;").Replace(">", "&gt;");//强显
                    text = text.Replace(tag, newtag);
                }


                /*-----盗取cookie的各种变体手法、及组合-----
                 \u0063\u006f\u006f\u006b\u0069\u0065
                 &#99;&#111;&#111;&#107;&#105;&#101;
                *--------------------------------------*/
                matchs = Regex.Matches(text, @"<[^<>]+((?:c|&#0*99;|\\u0063)(?:o|&#0*111;|\\u006f)(?:o|&#0*111;|\\u006f)(?:k|&#0*107;|\\u006b)(?:i|&#0*105;|\\u0069)(?:e|&#0*101;|\\u0065))+[^<>]+>", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newTag = Regex.Replace(tag, @"(?:c|&#0*99;|\\u0063)(?:o|&#0*111;|\\u006f)(?:o|&#0*111;|\\u006f)(?:k|&#0*107;|\\u006b)(?:i|&#0*105;|\\u0069)(?:e|&#0*101;|\\u0065)", "c_o_o_k_i_e", RegexOptions.IgnoreCase);
                    //string newTag = tag.Replace("<", "&lt;").Replace(">", "&gt;");//强显
                    text = text.Replace(tag, newTag);
                }

                //去ie6 css expression
                text = Regex.Replace(text, "expression", "XssStyle", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"(?:e|ｅ|\\x?0*65|\\x?0*45)\s*(?:x|ｘ|\\x?0*78|\\x?0*58)\s*(?:p|ｐ|\\x?0*70|\\x?0*50)\s*(?:r|ｒ|\\x?0*72|\\x?0*52)\s*(?:e|ｅ|\\x?0*65|\\x?0*45)\s*(?:s|ｓ|\\x?0*73|\\x?0*53)\s*(?:s|ｓ|\\x?0*73|\\x?0*53)\s*(?:i|ｉ|\\x?0*69|\\x?0*49)\s*(?:o|ｏ|\\x?0*6f|\\x?0*4f)\s*(?:n|ｎ|\\x?0*6e|\\x?0*4e)", "experssion", RegexOptions.IgnoreCase);
                text = FilterStyleWhiteList(text);

                // \\+|\/+|&+|\s+|#+|\t+|\r+|\n+

                //样式
                text = Regex.Replace(text, "<style.*?>[\\s\\S]*?<\\/style>", "<style></style>", RegexOptions.IgnoreCase);
                //脚本块
                text = Regex.Replace(text, "<script.*?>[\\s\\S]*?<\\/script>", "XSS_SCRIPT", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, "<noscript.*?>[\\s\\S]*?<\\/noscript>", "XSS_NOSCRIPT", RegexOptions.IgnoreCase);
                //on<event>等脚本
                text = Regex.Replace(text, @"\bon([a-zA-Z]{3,16}\s*=)", "XXS_$1", RegexOptions.IgnoreCase);

                text = Regex.Replace(text, @"(script|frame|form|meta|behavior|link|base|style)([\s|:|>])+", "$1XSS$2", RegexOptions.IgnoreCase);

                //控件
                text = Regex.Replace(text, "<select.*?>[\\s\\S]*?<\\/select>", "<font></font>", RegexOptions.IgnoreCase);
                //对象
                text = Regex.Replace(text, "<object.*?>[\\s\\S]*?<\\/object>", "XSS OBJECT", RegexOptions.IgnoreCase);
                //HTML注释
                text = Regex.Replace(text, @"<!--[\s\S]*?-->", "<font></font>", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"<(\s*!\s*--)", "＜$1", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"(--\s*)>", "$1＞", RegexOptions.IgnoreCase);
                //<br>标准化
                text = Regex.Replace(text, "\\<br[^\\>]*>", "<br />", RegexOptions.IgnoreCase);

                //取消锚点
                //html = Regex.Replace(html, @"<\s*a\s*name\s*=\s*['""\s]?[^'""\s/>]*?['""\s]?\s*>(.*?)<\s*/\s*a\s*>", "$1", RegexOptions.IgnoreCase);

                //IFRAME
                text = Regex.Replace(text, @"<\s*iframe.*iframe\s*>", "XSS_IFRAME", RegexOptions.IgnoreCase);

                //加embed安全过过滤
                text = Regex.Replace(text, @"allowscriptaccess", "all0wscriptaccess", RegexOptions.IgnoreCase);

                text = text.Replace("fromCharCode", "formCharCode");
                text = text.Replace("eval", "ｅｖａｌ");
                text = Regex.Replace(text, @"error", "ｅｒｒｏｒ", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"\bcookie\b", "ｃｏｏｋｉｅ", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"\blocation\b", "Location", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"\.open\b|=\s*open\b|\bopen\s*\(", "xsso_pen", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"\.navigate\b|=\s*navigate\b|\bnavigate\s*\(", "xssn_avigate", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"\.write\b", "xssw_rite", RegexOptions.IgnoreCase);
                //禁用data协议
                text = Regex.Replace(text, @"(=\s*['""]?\s*)data(\s*\:)", "$1XssData$2", RegexOptions.IgnoreCase);
                text = Regex.Replace(text, @"(<\s*\w+[^>]*?\b)data(\s*=[^>]+?>)", "$1XssData$2", RegexOptions.IgnoreCase);
                return text;
            }
            catch
            {
                return text;
            }
        }

        /// <summary>
        ///非富文本过滤Xss
        /// </summary>
        /// <param name="text">要过滤的参数</param>
        /// <returns></returns>
        public static string Filter(string text)
        {
            try
            {
                if (text == null || text.Length < 1)
                {
                    return text;
                }
                //剔除<键
                var matchs = Regex.Matches(text, @"((?:&#0*60;|\\u003c|&#x0*3c;|<|&lt;|\\x0*3c;|\\0*74;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"((?:&#0*62;|\\u003e|&#x0*3e;|>|&gt;|\\x0*3e;|\\0*76;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"((?:&#0*34;|\\u0022|&#x0*22;|" + "\"" + @"|\\x0*22;|\\0*42;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"((?:&#0*39;|\\u0027|&#x0*27;|'|\\x0*28;|\\0*47;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"((?:&#0*40;|\\u0028|&#x0*28;|\(|\\x0*28;|\\0*50;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
                matchs = Regex.Matches(text, @"((?:&#0*41;|\\u0029|&#x0*29;|\)|\\x0*29;|\\0*51;))", RegexOptions.IgnoreCase);
                for (int i = 0; i < matchs.Count; i++)
                {
                    string tag = matchs[i].Value;
                    string newtag = tag.Replace(matchs[i].Groups[1].Value, "");
                    text = text.Replace(tag, newtag);
                }
            }
            catch
            {
                return text;
            }
            return text;
        }

        /// <summary>
        /// style抗干扰+白名单解决方案(img签有单独提纯方案，本方法不处理IMG签的XSS注入)
        /// </summary>
        /// <param name="html"></param>
        /// <param name="allowStylePropertys">白名单style属性以逗号分隔（为null值取默认配置；为"*"时全开放，为""时全关）</param>
        /// <returns></returns>
        private static string FilterStyleWhiteList(string html, string allowStylePropertys = null)
        {
            if (string.IsNullOrEmpty(html) || html.Trim().Length < 1)
            {
                return html;
            }
            if (allowStylePropertys == null)
            {
                allowStylePropertys = DEFAULT_ALLOW_STYLE_PROPERTYS;
            }
            //去多style
            html = Regex.Replace(html, @"<[^<]*(?:\bstyle\s*=[^>]*)(?:\bstyle\s*=[^>]*)>", "<manystyle></manystyle>", RegexOptions.IgnoreCase);

            //去IE style注释注入
            string reg = @"<[^>]+/\*.*?\*/[^>]*>";
            var matchs = Regex.Matches(html, reg, RegexOptions.IgnoreCase);
            for (int i = 0; i < matchs.Count; i++)
            {
                html = html.Replace(matchs[i].Value, "<p>Suspected as a style XSS</p>");
            }

            //去非标准style
            reg = @"<[^>]+\bstyle\s*=\s*[^""'][^>]*>";
            matchs = Regex.Matches(html, reg, RegexOptions.IgnoreCase);
            for (int i = 0; i < matchs.Count; i++)
            {
                string tag = matchs[i].Value;
                string newtag = Regex.Replace(tag, @"style", @"style9", RegexOptions.IgnoreCase);
                html = html.Replace(tag, newtag);
            }


            if (allowStylePropertys == "*")//全开
            {
                return html;
            }
            if (string.IsNullOrEmpty(allowStylePropertys) || allowStylePropertys.Trim().Length < 1)//全关
            {
                html = Regex.Replace(html, @"(<(?!img)[^<]*)(?:\bstyle\s*=\s*\""[^\""]+\"")([^>]*>)", "$1 $2", RegexOptions.IgnoreCase);
                html = Regex.Replace(html, @"(<(?!img)[^<]*)(?:\bstyle\s*=\s*\'[^\']+\')([^>]*>)", "$1 $2", RegexOptions.IgnoreCase);
                return html;
            }

            string[] propertys = allowStylePropertys.ToLower().Split(',', '，');

            reg = @"<(?!img)[^<]*(\bstyle\s*=\s*\""[^\""]+\"")[^>]*>";
            matchs = Regex.Matches(html, reg, RegexOptions.IgnoreCase);


            for (int i = 0; i < matchs.Count; i++)
            {
                string tag = matchs[i].Value;
                Match styleM = Regex.Match(tag, @"\bstyle\s*=\s*""(.*?)""", RegexOptions.IgnoreCase);
                string styleFull = styleM.Value;
                string styleCnt = styleM.Groups[1].Value;
                string newStyleCnt = "";
                foreach (string p in styleCnt.Split(';'))
                {
                    string[] arr = p.Split(':');
                    if (arr.Length == 2)
                    {
                        string pk = arr[0].ToLower().Replace(" ", "").Replace("　", "");
                        if (Contains(propertys, pk))
                        {
                            //string pv = arr[1].Replace(@"\", "＼").Replace("&", "＆").Replace("#", "＃").Replace("{", "｛").Replace("}", "｝").Replace("(", "（").Replace(")", "）").Replace("script", "s_cript");
                            string pv = arr[1].Replace("#", "＃");
                            if (pv.Contains("/") || pv.Contains("\\")
                                || pv.Contains("&")
                                || pv.Contains("{")
                                || pv.Contains("}")
                                || pv.Contains("(")
                                || pv.Contains(")")
                                || pv.Length > 30
                                )
                            {
                                continue;
                            }
                            newStyleCnt += (pk + ":" + pv + ";");
                        }
                    }

                }
                string newStyleFull = " ";
                if (!IsEmpty(newStyleCnt))
                {
                    newStyleCnt = Regex.Replace(newStyleCnt, @"color\s*:\s*＃", "color:#", RegexOptions.IgnoreCase);
                    newStyleFull = Fmt("style=\"{0}\"", newStyleCnt);
                }
                string newtag = tag.Replace(styleFull, newStyleFull);
                html = html.Replace(tag, newtag);
            }

            reg = @"<(?!img)[^<]*(\bstyle\s*=\s*\'[^\']+\')[^>]*>";
            matchs = Regex.Matches(html, reg, RegexOptions.IgnoreCase);
            for (int i = 0; i < matchs.Count; i++)
            {
                string tag = matchs[i].Value;
                Match styleM = Regex.Match(tag, @"\bstyle\s*=\s*'(.*?)'", RegexOptions.IgnoreCase);
                string styleFull = styleM.Value;
                string styleCnt = styleM.Groups[1].Value;
                string newStyleCnt = "";
                foreach (string p in styleCnt.Split(';'))
                {
                    string[] arr = p.Split(':');
                    if (arr.Length == 2)
                    {
                        string pk = arr[0].ToLower();
                        if (Contains(propertys, pk))
                        {
                            //string pv = arr[1].Replace(@"\", "＼").Replace("&", "＆").Replace("#", "＃").Replace("{", "｛").Replace("}", "｝").Replace("(", "（").Replace(")", "）").Replace("script", "s_cript");
                            string pv = arr[1].Replace("#", "＃");
                            if (pv.Contains("/") || pv.Contains("\\")
                                || pv.Contains("&")
                                || pv.Contains("{")
                                || pv.Contains("}")
                                || pv.Contains("(")
                                || pv.Contains(")")
                                || pv.Length > 30
                                )
                            {
                                continue;
                            }
                            newStyleCnt += (pk + ":" + pv + ";");
                        }
                    }

                }
                string newStyleFull = " ";

                if (!IsEmpty(newStyleCnt))
                {
                    newStyleCnt = Regex.Replace(newStyleCnt, @"color\s*:\s*＃", "color:#", RegexOptions.IgnoreCase);
                    newStyleFull = Fmt("style=\"{0}\"", newStyleCnt);
                }
                string newtag = tag.Replace(styleFull, newStyleFull);
                html = html.Replace(tag, newtag);
            }

            return html;
        }

        private static bool Contains(string[] propertys, string pk)
        {
            return System.Array.Exists(propertys, delegate (string s) { return s.Equals(pk); });
        }


        /// <summary>
        /// String.Format的缩写形式，格式项替换为数组元素
        /// </summary>
        /// <param name="str">目标字符串</param>
        /// <param name="args">参数列表 或 储值数组</param>
        /// <returns></returns>
        public static string Fmt(string str, params object[] args)
        {
            return IsEmpty(str) ? str : String.Format(str, args);
        }

        /// <summary>
        /// 测试对象是否为空，包括：null值、空白、纯空格的字符串(包括全角空格)、长度为0的集合类型，匀视为空
        /// </summary>
        /// <param name="x">要测试的对象</param>
        /// <param name="SpaceIsEmpty">若对象为String时，默认视“空格”为空(包括全角空格)</param>
        /// <returns>为空返回true,非常返回false</returns>
        public static bool IsEmpty(object x, bool SpaceIsEmpty = true)
        {
            return x == null
                || (x is String && x.ToString() == "")
                || (x is String && SpaceIsEmpty && x.ToString().Trim().Replace("　", "") == "")
                || (x is ICollection && ((ICollection)x).Count == 0);
        }

    }
}
